// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
#pragma once

#include <iomanip>
#include <pollux/common/base/exceptions.h>
#include <pollux/core/i_typed_expr.h>
#include <pollux/vector/base_vector.h>

namespace kumo::pollux::core {
    class InputTypedExpr : public ITypedExpr {
    public:
        explicit InputTypedExpr(TypePtr type) : ITypedExpr{std::move(type)} {
        }

        bool operator==(const ITypedExpr &other) const final {
            const auto *casted = dynamic_cast<const InputTypedExpr *>(&other);
            return casted != nullptr;
        }

        std::string toString() const override {
            return "ROW";
        }

        size_t localHash() const override {
            static const size_t kBaseHash = std::hash<const char *>()("InputTypedExpr");
            return kBaseHash;
        }

        TypedExprPtr rewriteInputNames(
            const std::unordered_map<std::string, TypedExprPtr> & /*mapping*/)
        const override {
            return std::make_shared<InputTypedExpr>(type());
        }

        void accept(
            const ITypedExprVisitor &visitor,
            ITypedExprVisitorContext &context) const override;

        melon::Dynamic serialize() const override;

        static TypedExprPtr create(const melon::Dynamic &obj, void *context);
    };

    class ConstantTypedExpr : public ITypedExpr {
    public:
        // Creates constant expression. For complex types, only
        // variant::null() value is supported.
        ConstantTypedExpr(TypePtr type, variant value)
            : ITypedExpr{std::move(type)}, value_{std::move(value)} {
        }

        // Creates constant expression of scalar or complex type. The value comes from
        // index zero.
        explicit ConstantTypedExpr(const VectorPtr &value)
            : ITypedExpr{value->type()},
              valueVector_{
                  value->is_constant_encoding()
                      ? value
                      : BaseVector::wrap_in_constant(1, 0, value)
              } {
        }

        std::string toString() const override {
            if (hasValueVector()) {
                return valueVector_->toString(0);
            }
            return value_.toJson(type());
        }

        size_t localHash() const override {
            static const size_t kBaseHash =
                    std::hash<const char *>()("ConstantTypedExpr");

            return bits::hashMix(
                kBaseHash,
                hasValueVector() ? valueVector_->hash_value_at(0) : value_.hash());
        }

        bool hasValueVector() const {
            return valueVector_ != nullptr;
        }

        /// Returns scalar value as variant if hasValueVector() is false.
        const variant &value() const {
            return value_;
        }

        /// Return constant value vector if hasValueVector() is true. Returns null
        /// otherwise.
        const VectorPtr &value_vector() const {
            return valueVector_;
        }

        bool isNull() const {
            if (hasValueVector()) {
                return valueVector_->is_null_at(0);
            }
            return value_.isNull();
        }

        VectorPtr toConstantVector(memory::MemoryPool *pool) const {
            if (valueVector_) {
                return valueVector_;
            }
            if (value_.isNull()) {
                return BaseVector::create_null_constant(type(), 1, pool);
            }
            return BaseVector::create_constant(type(), value_, 1, pool);
        }

        const std::vector<TypedExprPtr> &inputs() const {
            static const std::vector<TypedExprPtr> kEmpty{};
            return kEmpty;
        }

        TypedExprPtr rewriteInputNames(
            const std::unordered_map<std::string, TypedExprPtr> & /*mapping*/)
        const override {
            if (hasValueVector()) {
                return std::make_shared<ConstantTypedExpr>(valueVector_);
            } else {
                return std::make_shared<ConstantTypedExpr>(type(), value_);
            }
        }

        void accept(
            const ITypedExprVisitor &visitor,
            ITypedExprVisitorContext &context) const override;

        bool equals(const ITypedExpr &other) const {
            const auto *casted = dynamic_cast<const ConstantTypedExpr *>(&other);
            if (!casted) {
                return false;
            }

            if (*this->type() != *casted->type()) {
                return false;
            }

            if (this->hasValueVector() != casted->hasValueVector()) {
                return false;
            }

            if (this->hasValueVector()) {
                return this->valueVector_->equal_value_at(casted->valueVector_.get(), 0, 0);
            }

            return this->value_ == casted->value_;
        }

        bool operator==(const ITypedExpr &other) const final {
            return this->equals(other);
        }

        bool operator==(const ConstantTypedExpr &other) const {
            return this->equals(other);
        }

        melon::Dynamic serialize() const override;

        static TypedExprPtr create(const melon::Dynamic &obj, void *context);

    private:
        const variant value_;
        const VectorPtr valueVector_;
    };

    using ConstantTypedExprPtr = std::shared_ptr<const ConstantTypedExpr>;

    /// Evaluates a scalar function or a special form.
    ///
    /// Supported special forms are: and, or, cast, try_cast, coalesce, if, switch,
    /// try. See registerFunctionCallToSpecialForms in
    /// expression/RegisterSpecialForm.h for the up-to-date list.
    ///
    /// Regular functions have the following properties: (1) return type is fully
    /// defined by function name and input types; (2) during evaluation all function
    /// arguments are evaluated first before the function itself is evaluated on the
    /// results, a failure to evaluate function argument prevents the function from
    /// being evaluated.
    ///
    /// Special forms are different from regular scalar functions as they do not
    /// always have the above properties.
    ///
    /// - CAST doesn't have (1): return type is not defined by input type as it is
    /// possible to cast VARCHAR to INTEGER, BOOLEAN, and many other types.
    /// - Conjuncts AND, OR don't have (2): these have logic to stop evaluating
    /// arguments if the outcome is already decided. For example, a > 10 AND b < 3
    /// applied to a = 0 and b = 0 is fully decided after evaluating a > 10. The
    /// result is FALSE. This is important not only from efficiency standpoint, but
    /// semantically as well. Not evaluating unnecessary arguments implicitly
    /// suppresses the errors that might have happened if evaluation proceeded. For
    /// example, a > 10 AND b / a > 1 would fail if both expressions were evaluated
    /// on a = 0.
    /// - Coalesce, if, switch also don't have (2): these also have logic to stop
    /// evaluating arguments if the outcome is already decided.
    /// - TRY doesn't have (2) either: it needs to capture and suppress errors
    /// received while evaluating the input.
    class CallTypedExpr : public ITypedExpr {
    public:
        /// @param type Return type.
        /// @param inputs List of input expressions. May be empty.
        /// @param name Name of the function or special form.
        CallTypedExpr(
            TypePtr type,
            std::vector<TypedExprPtr> inputs,
            std::string name)
            : ITypedExpr{std::move(type), std::move(inputs)},
              name_(std::move(name)) {
        }

        virtual const std::string &name() const {
            return name_;
        }

        TypedExprPtr rewriteInputNames(
            const std::unordered_map<std::string, TypedExprPtr> &mapping)
        const override {
            return std::make_shared<CallTypedExpr>(
                type(), rewriteInputsRecursive(mapping), name_);
        }

        std::string toString() const override {
            std::string str{};
            str += name();
            str += "(";
            for (size_t i = 0; i < inputs().size(); ++i) {
                auto &input = inputs().at(i);
                if (i != 0) {
                    str += ",";
                }
                str += input->toString();
            }
            str += ")";
            return str;
        }

        size_t localHash() const override {
            static const size_t kBaseHash = std::hash<const char *>()("CallTypedExpr");
            return bits::hashMix(kBaseHash, std::hash<std::string>()(name_));
        }

        void accept(
            const ITypedExprVisitor &visitor,
            ITypedExprVisitorContext &context) const override;

        bool operator==(const ITypedExpr &other) const override {
            const auto *casted = dynamic_cast<const CallTypedExpr *>(&other);
            if (!casted) {
                return false;
            }
            return operator==(*casted);
        }

        bool operator==(const CallTypedExpr &other) const {
            if (other.name() != this->name()) {
                return false;
            }
            if (*other.type() != *this->type()) {
                return false;
            }
            return std::equal(
                this->inputs().begin(),
                this->inputs().end(),
                other.inputs().begin(),
                other.inputs().end(),
                [](const auto &p1, const auto &p2) { return *p1 == *p2; });
        }

        melon::Dynamic serialize() const override;

        static TypedExprPtr create(const melon::Dynamic &obj, void *context);

    private:
        const std::string name_;
    };

    using CallTypedExprPtr = std::shared_ptr<const CallTypedExpr>;

    /// Represents a leaf in an expression tree specifying input column by name.
    class FieldAccessTypedExpr : public ITypedExpr {
    public:
        /// Used as a leaf in an expression tree specifying input column by name.
        FieldAccessTypedExpr(TypePtr type, std::string name)
            : ITypedExpr{std::move(type)},
              name_(std::move(name)),
              isInputColumn_(true) {
        }

        /// Used as a dereference expression which selects a subfield in a struct by
        /// name.
        FieldAccessTypedExpr(TypePtr type, TypedExprPtr input, std::string name)
            : ITypedExpr{std::move(type), {std::move(input)}},
              name_(std::move(name)),
              isInputColumn_(dynamic_cast<const InputTypedExpr *>(inputs()[0].get())) {
        }

        const std::string &name() const {
            return name_;
        }

        TypedExprPtr rewriteInputNames(
            const std::unordered_map<std::string, TypedExprPtr> &mapping)
        const override {
            if (inputs().empty()) {
                auto it = mapping.find(name_);
                return it != mapping.end()
                           ? it->second
                           : std::make_shared<FieldAccessTypedExpr>(type(), name_);
            }

            auto newInputs = rewriteInputsRecursive(mapping);
            POLLUX_CHECK_EQ(1, newInputs.size());
            // Only rewrite name if input in InputTypedExpr. Rewrite in other
            // cases(like dereference) is unsound.
            if (!std::dynamic_pointer_cast<const InputTypedExpr>(newInputs[0])) {
                return std::make_shared<FieldAccessTypedExpr>(
                    type(), newInputs[0], name_);
            }
            auto it = mapping.find(name_);
            auto newName = name_;
            if (it != mapping.end()) {
                if (auto name = std::dynamic_pointer_cast<const FieldAccessTypedExpr>(
                    it->second)) {
                    newName = name->name();
                }
            }
            return std::make_shared<FieldAccessTypedExpr>(
                type(), newInputs[0], newName);
        }

        std::string toString() const override {
            std::stringstream ss;
            ss << std::quoted(name(), '"', '"');
            if (inputs().empty()) {
                return fmt::format("{}", ss.str());
            }

            return fmt::format("{}[{}]", inputs()[0]->toString(), ss.str());
        }

        size_t localHash() const override {
            static const size_t kBaseHash =
                    std::hash<const char *>()("FieldAccessTypedExpr");
            return bits::hashMix(kBaseHash, std::hash<std::string>()(name_));
        }

        void accept(
            const ITypedExprVisitor &visitor,
            ITypedExprVisitorContext &context) const override;

        bool operator==(const ITypedExpr &other) const final {
            const auto *casted = dynamic_cast<const FieldAccessTypedExpr *>(&other);
            if (!casted) {
                return false;
            }
            return operator==(*casted);
        }

        bool operator==(const FieldAccessTypedExpr &other) const {
            if (other.name_ != this->name_) {
                return false;
            }
            if (*other.type() != *this->type()) {
                return false;
            }
            return std::equal(
                this->inputs().begin(),
                this->inputs().end(),
                other.inputs().begin(),
                other.inputs().end(),
                [](const auto &p1, const auto &p2) { return *p1 == *p2; });
        }

        /// Is this FieldAccess accessing an input column or a field in a struct.
        bool isInputColumn() const {
            return isInputColumn_;
        }

        melon::Dynamic serialize() const override;

        static TypedExprPtr create(const melon::Dynamic &obj, void *context);

    private:
        const std::string name_;
        const bool isInputColumn_;
    };

    using FieldAccessTypedExprPtr = std::shared_ptr<const FieldAccessTypedExpr>;

    /// Represents a dereference expression which selects a subfield in a struct by
    /// name.
    class DereferenceTypedExpr : public ITypedExpr {
    public:
        DereferenceTypedExpr(TypePtr type, TypedExprPtr input, uint32_t index)
            : ITypedExpr{std::move(type), {std::move(input)}}, index_(index) {
            // Make sure this isn't being used to access a top level column.
            POLLUX_USER_CHECK_NULL(
                std::dynamic_pointer_cast<const InputTypedExpr>(inputs()[0]));
        }

        uint32_t index() const {
            return index_;
        }

        const std::string &name() const {
            return inputs()[0]->type()->as_row().nameOf(index_);
        }

        TypedExprPtr rewriteInputNames(
            const std::unordered_map<std::string, TypedExprPtr> &mapping)
        const override {
            auto newInputs = rewriteInputsRecursive(mapping);
            POLLUX_CHECK_EQ(1, newInputs.size());

            return std::make_shared<DereferenceTypedExpr>(type(), newInputs[0], index_);
        }

        std::string toString() const override {
            return fmt::format("{}[{}]", inputs()[0]->toString(), name());
        }

        size_t localHash() const override {
            static const size_t kBaseHash =
                    std::hash<const char *>()("DereferenceTypedExpr");
            return bits::hashMix(kBaseHash, index_);
        }

        void accept(
            const ITypedExprVisitor &visitor,
            ITypedExprVisitorContext &context) const override;

        bool operator==(const ITypedExpr &other) const final {
            const auto *casted = dynamic_cast<const DereferenceTypedExpr *>(&other);
            if (!casted) {
                return false;
            }
            return operator==(*casted);
        }

        bool operator==(const DereferenceTypedExpr &other) const {
            if (other.index_ != this->index_) {
                return false;
            }
            return std::equal(
                this->inputs().begin(),
                this->inputs().end(),
                other.inputs().begin(),
                other.inputs().end(),
                [](const auto &p1, const auto &p2) { return *p1 == *p2; });
        }

        melon::Dynamic serialize() const override;

        static TypedExprPtr create(const melon::Dynamic &obj, void *context);

    private:
        const uint32_t index_;
    };

    using DereferenceTypedExprPtr = std::shared_ptr<const DereferenceTypedExpr>;

    /// Evaluates a list of expressions to produce a row.
    class ConcatTypedExpr : public ITypedExpr {
    public:
        ConcatTypedExpr(
            const std::vector<std::string> &names,
            const std::vector<TypedExprPtr> &inputs)
            : ITypedExpr{toType(names, inputs), inputs} {
        }

        TypedExprPtr rewriteInputNames(
            const std::unordered_map<std::string, TypedExprPtr> &mapping)
        const override {
            return std::make_shared<ConcatTypedExpr>(
                type()->as_row().names(), rewriteInputsRecursive(mapping));
        }

        std::string toString() const override {
            std::string str{};
            str += "CONCAT(";
            for (size_t i = 0; i < inputs().size(); ++i) {
                auto &input = inputs().at(i);
                if (i != 0) {
                    str += ",";
                }
                str += input->toString();
            }
            str += ")";
            return str;
        }

        size_t localHash() const override {
            static const size_t kBaseHash = std::hash<const char *>()("ConcatTypedExpr");
            return kBaseHash;
        }

        void accept(
            const ITypedExprVisitor &visitor,
            ITypedExprVisitorContext &context) const override;

        bool operator==(const ITypedExpr &other) const override {
            const auto *casted = dynamic_cast<const ConcatTypedExpr *>(&other);
            if (!casted) {
                return false;
            }
            return operator==(*casted);
        }

        bool operator==(const ConcatTypedExpr &other) const {
            if (*other.type() != *this->type()) {
                return false;
            }
            return std::equal(
                this->inputs().begin(),
                this->inputs().end(),
                other.inputs().begin(),
                other.inputs().end(),
                [](const auto &p1, const auto &p2) { return *p1 == *p2; });
        }

        melon::Dynamic serialize() const override;

        static TypedExprPtr create(const melon::Dynamic &obj, void *context);

    private:
        static TypePtr toType(
            const std::vector<std::string> &names,
            const std::vector<TypedExprPtr> &expressions) {
            std::vector<TypePtr> children{};
            std::vector<std::string> namesCopy{};
            for (size_t i = 0; i < names.size(); ++i) {
                namesCopy.push_back(names.at(i));
                children.push_back(expressions.at(i)->type());
            }
            return ROW(std::move(namesCopy), std::move(children));
        }
    };

    using ConcatTypedExprPtr = std::shared_ptr<const ConcatTypedExpr>;

    class LambdaTypedExpr : public ITypedExpr {
    public:
        LambdaTypedExpr(RowTypePtr signature, TypedExprPtr body)
            : ITypedExpr(std::make_shared<FunctionType>(
                  std::vector<TypePtr>(signature->children()),
                  body->type())),
              signature_(signature),
              body_(body) {
        }

        const RowTypePtr &signature() const {
            return signature_;
        }

        const TypedExprPtr &body() const {
            return body_;
        }

        TypedExprPtr rewriteInputNames(
            const std::unordered_map<std::string, TypedExprPtr> &mapping)
        const override {
            for (const auto &name: signature_->names()) {
                if (mapping.count(name)) {
                    POLLUX_USER_FAIL("Ambiguous variable: {}", name);
                }
            }
            return std::make_shared<LambdaTypedExpr>(
                signature_, body_->rewriteInputNames(mapping));
        }

        std::string toString() const override {
            return fmt::format(
                "lambda {} -> {}", signature_->toString(), body_->toString());
        }

        size_t localHash() const override {
            static const size_t kBaseHash = std::hash<const char *>()("LambdaTypedExpr");
            return bits::hashMix(kBaseHash, body_->hash());
        }

        void accept(
            const ITypedExprVisitor &visitor,
            ITypedExprVisitorContext &context) const override;

        bool operator==(const ITypedExpr &other) const override {
            const auto *casted = dynamic_cast<const LambdaTypedExpr *>(&other);
            if (!casted) {
                return false;
            }
            if (*casted->type() != *this->type()) {
                return false;
            }
            return *signature_ == *casted->signature_ && *body_ == *casted->body_;
        }

        melon::Dynamic serialize() const override;

        static TypedExprPtr create(const melon::Dynamic &obj, void *context);

    private:
        const RowTypePtr signature_;
        const TypedExprPtr body_;
    };

    using LambdaTypedExprPtr = std::shared_ptr<const LambdaTypedExpr>;

    /// Converts input values to specified type.
    class CastTypedExpr : public ITypedExpr {
    public:
        /// @param type Type to convert to. This is the return type of the CAST
        /// expresion.
        /// @param input Single input. The type of input is referred to as from-type
        /// and expected to be different from to-type.
        /// @param nullOnFailure Whether to suppress cast errors and return null.
        CastTypedExpr(
            const TypePtr &type,
            const TypedExprPtr &input,
            bool nullOnFailure)
            : ITypedExpr{type, {input}}, nullOnFailure_(nullOnFailure) {
        }

        CastTypedExpr(
            const TypePtr &type,
            const std::vector<TypedExprPtr> &inputs,
            bool nullOnFailure)
            : ITypedExpr{type, inputs}, nullOnFailure_(nullOnFailure) {
            POLLUX_USER_CHECK_EQ(
                1, inputs.size(), "Cast expression requires exactly one input");
        }

        TypedExprPtr rewriteInputNames(
            const std::unordered_map<std::string, TypedExprPtr> &mapping)
        const override {
            return std::make_shared<CastTypedExpr>(
                type(), rewriteInputsRecursive(mapping), nullOnFailure_);
        }

        std::string toString() const override {
            if (nullOnFailure_) {
                return fmt::format(
                    "try_cast {} as {}", inputs()[0]->toString(), type()->toString());
            } else {
                return fmt::format(
                    "cast {} as {}", inputs()[0]->toString(), type()->toString());
            }
        }

        size_t localHash() const override {
            static const size_t kBaseHash = std::hash<const char *>()("CastTypedExpr");
            return bits::hashMix(kBaseHash, std::hash<bool>()(nullOnFailure_));
        }

        void accept(
            const ITypedExprVisitor &visitor,
            ITypedExprVisitorContext &context) const override;

        bool operator==(const ITypedExpr &other) const override {
            const auto *otherCast = dynamic_cast<const CastTypedExpr *>(&other);
            if (!otherCast) {
                return false;
            }
            if (inputs().empty()) {
                return type() == otherCast->type() && otherCast->inputs().empty() &&
                       nullOnFailure_ == otherCast->nullOnFailure();
            }
            return *type() == *otherCast->type() &&
                   *inputs()[0] == *otherCast->inputs()[0] &&
                   nullOnFailure_ == otherCast->nullOnFailure();
        }

        bool nullOnFailure() const {
            return nullOnFailure_;
        }

        melon::Dynamic serialize() const override;

        static TypedExprPtr create(const melon::Dynamic &obj, void *context);

    private:
        // Suppress exception and return null on failure to cast.
        const bool nullOnFailure_;
    };

    using CastTypedExprPtr = std::shared_ptr<const CastTypedExpr>;

    /// A collection of convenience methods for working with expressions.
    class TypedExprs {
    public:
        /// Returns true if 'expr' is a field access expression.
        static bool isFieldAccess(const TypedExprPtr &expr) {
            return dynamic_cast<const FieldAccessTypedExpr *>(expr.get()) != nullptr;
        }

        /// Returns 'expr' as FieldAccessTypedExprPtr or null if not field access
        /// expression.
        static FieldAccessTypedExprPtr asFieldAccess(const TypedExprPtr &expr) {
            return std::dynamic_pointer_cast<const FieldAccessTypedExpr>(expr);
        }

        /// Returns true if 'expr' is a constant expression.
        static bool isConstant(const TypedExprPtr &expr) {
            return dynamic_cast<const ConstantTypedExpr *>(expr.get()) != nullptr;
        }

        /// Returns 'expr' as ConstantTypedExprPtr or null if not a constant
        /// expression.
        static ConstantTypedExprPtr asConstant(const TypedExprPtr &expr) {
            return std::dynamic_pointer_cast<const ConstantTypedExpr>(expr);
        }

        /// Returns true if 'expr' is a lambda expression.
        static bool isLambda(const TypedExprPtr &expr) {
            return dynamic_cast<const LambdaTypedExpr *>(expr.get()) != nullptr;
        }

        /// Returns 'expr' as LambdaTypedExprPtr or null if not a lambda expression.
        static LambdaTypedExprPtr asLambda(const TypedExprPtr &expr) {
            return std::dynamic_pointer_cast<const LambdaTypedExpr>(expr);
        }
    };

    class ITypedExprVisitorContext {
    public:
        virtual ~ITypedExprVisitorContext() = default;
    };

    class ITypedExprVisitor {
    public:
        virtual ~ITypedExprVisitor() = default;

        virtual void visit(const CallTypedExpr &expr, ITypedExprVisitorContext &ctx)
        const = 0;

        virtual void visit(const CastTypedExpr &expr, ITypedExprVisitorContext &ctx)
        const = 0;

        virtual void visit(const ConcatTypedExpr &expr, ITypedExprVisitorContext &ctx)
        const = 0;

        virtual void visit(
            const ConstantTypedExpr &expr,
            ITypedExprVisitorContext &ctx) const = 0;

        virtual void visit(
            const DereferenceTypedExpr &expr,
            ITypedExprVisitorContext &ctx) const = 0;

        virtual void visit(
            const FieldAccessTypedExpr &expr,
            ITypedExprVisitorContext &ctx) const = 0;

        virtual void visit(const InputTypedExpr &expr, ITypedExprVisitorContext &ctx)
        const = 0;

        virtual void visit(const LambdaTypedExpr &expr, ITypedExprVisitorContext &ctx)
        const = 0;

    protected:
        void visitInputs(const ITypedExpr &expr, ITypedExprVisitorContext &ctx)
        const {
            for (auto &child: expr.inputs()) {
                child->accept(*this, ctx);
            }
        }
    };

    /// An implementation of ITypedExprVisitor that can be used to build a visitor
    /// that handles only specific types of expressions.
    class DefaultTypedExprVisitor : public ITypedExprVisitor {
    public:
        void visit(const CallTypedExpr &expr, ITypedExprVisitorContext &ctx)
        const override {
            visitInputs(expr, ctx);
        }

        void visit(const CastTypedExpr &expr, ITypedExprVisitorContext &ctx)
        const override {
            visitInputs(expr, ctx);
        }

        void visit(const ConcatTypedExpr &expr, ITypedExprVisitorContext &ctx)
        const override {
            visitInputs(expr, ctx);
        }

        void visit(const ConstantTypedExpr &expr, ITypedExprVisitorContext &ctx)
        const override {
            visitInputs(expr, ctx);
        }

        void visit(const DereferenceTypedExpr &expr, ITypedExprVisitorContext &ctx)
        const override {
            visitInputs(expr, ctx);
        }

        void visit(const FieldAccessTypedExpr &expr, ITypedExprVisitorContext &ctx)
        const override {
            visitInputs(expr, ctx);
        }

        void visit(const InputTypedExpr &expr, ITypedExprVisitorContext &ctx)
        const override {
            visitInputs(expr, ctx);
        }

        void visit(const LambdaTypedExpr &expr, ITypedExprVisitorContext &ctx)
        const override {
            visitInputs(expr, ctx);
        }
    };
} // namespace kumo::pollux::core
